package iterativo.gals;

import iterativo.compilador.NoPilhaElementos;
import iterativo.compilador.Operacoes;

import java.util.ArrayList;
import java.util.List;

/**
 * Classe que executa as ações semânticas da linguagem intermediária iterativa. O resultado é
 * representado por listas de comandos que identificam os rótulos e ações que devem ser executados.
 * Esse resultado é então convertido em instruções rotuladas (programa monolítico).
 * 
 * @author Douglas Ruskowski Haase
 * 
 */
public class SemanticoIterativo implements Constants {

    private int enderecoAtual = 0;

    private PilhaRotulos pilhaRotulosEntao = new PilhaRotulos();

    private PilhaRotulos pilhaRotulosSenao = new PilhaRotulos();

    private RepresentacaoIterativa iterativa = new RepresentacaoIterativa();

    private List<String> parametros = new ArrayList<String>();

    private List<String> retornos = new ArrayList<String>();

    private String nomePrograma;

    public void executeAction(int action, Token token) throws SemanticError {

	switch (action) {

	case 1:
	    actionOne(token);
	    break;

	case 2:
	    actionTwo(token);
	    break;

	case 3:
	    actionThree(token);
	    break;

	case 4:
	    actionFour(token);
	    break;

	case 5:
	    actionFive(token);
	    break;

	case 6:
	    actionSix(token);
	    break;

	case 7:
	    actionSeven(token);
	    break;

	case 8:
	    actionEight(token);
	    break;

	case 9:
	    actionNine(token);
	    break;

	case 10:
	    actionTen(token);
	    break;

	case 11:
	    actionEleven(token);
	    break;

	case 12:
	    actionTwelve(token);
	    break;

	case 13:
	    actionThirteen(token);
	    break;

	case 14:
	    actionFourteen(token);
	    break;

	case 15:
	    actionFifteen(token);
	    break;

	case 16:
	    actionSixteen(token);
	    break;

	default:
	    break;
	}

    }

    private void actionSixteen(Token token) {
	nomePrograma = token.getLexeme();
    }

    // Ação 1: incrementa '1' no endereço que inicia em '0'.
    private void actionOne(Token token) throws SemanticError {
	incrementaEndereco();
    }

    /**
     * Terminou (TESTE).
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionTwo(Token token) {

	NoPilhaElementos no = pilhaRotulosEntao.popNoPilha();
	NoPilhaElementos no2 = pilhaRotulosSenao.popNoPilha();
	NoPilhaElementos no3 = pilhaRotulosEntao.popNoPilha();

	iterativa.getC4().put(no.getValor(), enderecoAtual);

	iterativa.getC5().put(no3.getValor(), no2.getValor());

    }

    /**
     * Terminou ATE COND.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionThree(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1= teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C5(endereco, sucessor)
	iterativa.getC5().put(enderecoAtual, enderecoAtual + 1);

	// Push(endereco)
	pilhaRotulosEntao.push(enderecoAtual);

	System.out.println(" push p1 " + enderecoAtual);

	incrementaEndereco();

    }

    /**
     * Ação 4: terminou BLOCO-REP do ATE.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionFour(Token token) {

	// C4(front(), endereco) atualiza saida do ATE
	int ate = pilhaRotulosEntao.getPrim();
	iterativa.getC4().put(ate, enderecoAtual);

	// C4(pred, pop()) atualiza a próxima instrução do último comando do
	// bloco 'ate'
	int saidaAte = pilhaRotulosEntao.pop();
	iterativa.getC4().put(getEnderecoPredecessor(), saidaAte);

	incrementaEndereco();

    }

    /**
     * Ação 5: terminou EQTO COND.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionFive(Token token) {

	// C0(endereco)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1 = teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	// Push(endereco)
	pilhaRotulosEntao.push(enderecoAtual);

	System.out.println(" push p1 " + enderecoAtual);

	incrementaEndereco();
    }

    /**
     * Ação 6: terminou BLOCO-REP do EQTO.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionSix(Token token) {

	// C5(front(), ender) lê topo da pilha e atualiza esse endereço com o
	// atual no C5
	int enquanto = pilhaRotulosEntao.getPrim();

	iterativa.getC5().put(enquanto, enderecoAtual);

	// C4(pred, pop()) atualiza a próxima instrução do último comando do
	// bloco 'enquanto'
	int saidaEqto = pilhaRotulosEntao.pop();
	iterativa.getC4().put(getEnderecoPredecessor(), saidaEqto-1);

	incrementaEndereco();

    }

    /**
     * 
     * Ação 7: encontrou um id no não-terminal CMD.
     * 
     * {C0(ender,ender)} = cria operação
     * 
     * {C2(ender, nr)} = receptor (registrador que será incrementado ou decrementado 1)
     * 
     * {C4(ender, succ)} = aponta para o próximo comando
     * 
     * @param token
     *            o token lido nessa ação.
     * 
     */
    private void actionSeven(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C2(nr)
	String stringRegistrador = token.getLexeme().substring(1,
							       token.getLexeme().length());

	iterativa.getC2()
	    .put(enderecoAtual, Integer.valueOf(stringRegistrador));

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);
    }

    /**
     * Ação 8: encontrou + ou - como operação de um id.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionEight(Token token) {

	// C1(3 ou 4) 3 = incremento; 4 = decremento
	String operacao = token.getLexeme();

	if (operacao.equals("+")) {
	    iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.INCREMENTO);

	} else {
	    iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.DECREMENTO);
	}

	incrementaEndereco();
    }

    /**
     * Ação 9: terminou 'SE COND' no teste.
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionNine(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(1) 1 = teste
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.TESTE);

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	// Push(ender)
	pilhaRotulosEntao.push(enderecoAtual);

	incrementaEndereco();

    }
    
    

    /**
     * Ação 10: encontrou SENAO
     * 
     * @param token
     *            o token lido nessa ação.
     */
    private void actionTen(Token token) {

	pilhaRotulosSenao.push(enderecoAtual);

	System.out.println(" push p2 " + enderecoAtual);

    }

    /**
     * Ação 11: encontrou 'COND' no teste.
     * 
     * @param token
     */
    private void actionEleven(Token token) {

	// C0(ender)
	iterativa.armazenaEnderecoAtualC0(enderecoAtual);

	// C1(2) 2 = atribuição de registrador
	iterativa.armazenaOperacaoC1(enderecoAtual, Operacoes.ATRIBUICAO);

	// C2(0) registrador de teste
	iterativa.armazenaReceptorC2(enderecoAtual, 0);

	// C3(nr)
	String valorRegistrador = token.getLexeme().substring(1,
							      token.getLexeme().length());

	iterativa.armazenaSenderC3(enderecoAtual,
				   Integer.valueOf(valorRegistrador));

	// C4(sucessor)
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);

	incrementaEndereco();

    }

    /**
     * Armazena os parâmetros.
     * 
     * @param token
     *            o token lido.
     */
    private void actionTwelve(Token token) {
	parametros.add(token.getLexeme());
    }

    private void actionThirteen(Token token) {
	retornos.add(token.getLexeme());
    }

    /**
     * Ação de padding/enchimento. Gera instrução vazia.
     * 
     * @param token
     */
    private void actionFourteen(Token token) {
	iterativa.getC0().add(enderecoAtual);
	iterativa.getC1().put(enderecoAtual, 0);
	iterativa.armazenaSucessorC4(enderecoAtual, enderecoAtual + 1);
    }

    /**
     * Acaba o ENTAO e empilha o endereço do enchimento.
     * 
     * @param token
     */
    private void actionFifteen(Token token) {
	actionFourteen(token);
	pilhaRotulosEntao.push(enderecoAtual);

	System.out.println(" push p1 " + enderecoAtual);

	incrementaEndereco();
    }

    /**
     * Incrementa em 1 o valor do endereço atual.
     */
    private void incrementaEndereco() {
	enderecoAtual++;
    }

    private int getEnderecoPredecessor() {
	return enderecoAtual - 1;
    }

    public RepresentacaoIterativa getIterativa() {
	return iterativa;
    }

    public String getNomePrograma() {
	return nomePrograma;
    }

    public String construirHeader() {
	String atributos = "";
	if (!parametros.isEmpty()) {
	    atributos = "(";
	    atributos = atributos + parametros.get(0);
	    for (int i = 1; i < parametros.size(); i++) {
		atributos = atributos + "," + parametros.get(i);
	    }
	    atributos = atributos + ")";
	}
	String returns = retornos.get(0);
	for (int i = 1; i < retornos.size(); i++) {
	    returns = returns + ", " + retornos.get(i);
	}

	String header = "programa " + nomePrograma + atributos + " -> " + returns;
	return header;
    }

}
